package org.activequant.data.retrieval.mappings;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.data.MarketDataEntity;
import org.activequant.core.mappings.IInstrumentSpecificationMapper;
import org.activequant.core.types.TimeFrame;
import org.activequant.data.retrieval.ISubscription;
import org.activequant.util.exceptions.SubscriptionException;
import org.activequant.util.pattern.events.IEventListener;

/**
 * An abstract class. SubscriptionSourceMappingProxy&lt;T extends MarketDataEntity&lt;T&gt;&gt;. Holds the following associated variables:
 * <ul>
 * <li>subscriptions(List&lt;ISubscription&lt;T&gt;&gt;)</li>
 * <li>mapper(IInstrumentSpecificationMapper)</li>
 * </ul>
 * <br>  
 * <b>History:</b><br>
 *  - [20.01.2008] Created (Erik Nijkamp)<br>
 *  
 *  @author Erik Nijkamp
 *
 */
public abstract class SubscriptionSourceMappingProxy<T extends MarketDataEntity<T>> {
	/**
	 * private List&lt;ISubscription&lt;T&gt;&gt; subscriptions = new ArrayList&lt;ISubscription&lt;T&gt;&gt;();
	 */
	private List<ISubscription<T>> subscriptions = new ArrayList<ISubscription<T>>();
	/**
	 * private IInstrumentSpecificationMapper mapper;
	 */
	private IInstrumentSpecificationMapper mapper;
	/**
	 * abstract method
	 * @param spec
	 * @param timeFrame
	 * @return
	 * @throws Exception
	 */
	protected abstract ISubscription<T> handleSubscribe(InstrumentSpecification spec, TimeFrame timeFrame) throws Exception;
	/**
	 * constructs a SubscriptionSourceMappingProxy&lt;T extends MarketDataEntity&lt;T&gt;&gt; using the given mapper(IInstrumentSpecificationMapper) to set its associated
	 * mapper(IInstrumentSpecificationMapper)
	 * @param mapper
	 */
	protected SubscriptionSourceMappingProxy(IInstrumentSpecificationMapper mapper) {
		this.mapper = mapper;
	}
	/**
	 * returns an ISubscription&lt;T&gt;[] array holding the associated subscriptions(List&lt;ISubscription&lt;T&gt;&gt;)
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public ISubscription<T>[] getSubscriptions() {
		return subscriptions.toArray(new ISubscription[] {});
	}
	/**
	 * Creates and returns a proxy-ied ISubscription&lt;T&gt; for the given spec(InstrumentSpecification) for a TIMEFRAME_1_TICK.<br/>
	 * It also adds that proxy-ied ISubscription&lt;T&gt; to the associated subscriptions(List&lt;ISubscription&lt;T&gt;&gt;).
	 * @param spec
	 * @return
	 * @throws Exception
	 */
	public ISubscription<T> subscribe(final InstrumentSpecification spec) throws Exception {
		return subscribe(spec, TimeFrame.TIMEFRAME_1_TICK);
	}
	/**
	 * Creates and returns a proxy-ied ISubscription&lt;T&gt; for the given spec(InstrumentSpecification) and the given timeFrame(TimeFrame).<br/>
	 * It also adds that proxy-ied ISubscription&lt;T&gt; to the associated subscriptions(List&lt;ISubscription&lt;T&gt;&gt;).
	 * @param spec
	 * @param timeFrame
	 * @return
	 * @throws Exception
	 */
	public ISubscription<T> subscribe(final InstrumentSpecification spec, final TimeFrame timeFrame) throws Exception {
		final InstrumentSpecification internal = mapper.mapToInternal(spec);
		final ISubscription<T> subscription = handleSubscribe(internal, timeFrame);
		/**
		 * An inline class, implements ISubscription&lt;T&gt;. Holds the following associated variables:
		 * <ul>
		 * <li>listeners(Map&lt;IEventListener&lt;T&gt;,IEventListener&lt;T&gt;&gt;)</li>
		 * <li>instrument(InstrumentSpecification)</li>
		 * </ul>
		 * @author Dimitar
		 *
		 */
		class ProxySubscription implements ISubscription<T> {
			/**
			 * Map&lt;IEventListener&lt;T&gt;,IEventListener&lt;T&gt;&gt; listeners = new IdentityHashMap&lt;IEventListener&lt;T&gt;,IEventListener&lt;T&gt;&gt;();
			 */
			Map<IEventListener<T>, IEventListener<T>> listeners = new IdentityHashMap<IEventListener<T>, IEventListener<T>>();
			/**
			 * private InstrumentSpecification instrument = null;
			 */
			private InstrumentSpecification instrument = null;
			/**
			 * constructs a ProxySubscription(implements ISubscription&lt;T&gt;) using the given spec(InstrumentSpecification) to set its associated instrument(InstrumentSpecification)
			 * @param spec
			 */
			ProxySubscription(InstrumentSpecification spec){
				this.instrument = spec;
			}
			/**
			 * returns the associated instrument(InstrumentSpecification)
			 */
			public InstrumentSpecification getInstrument() {
				return instrument;
			}
			/**
			 * activates the previously created subscription(ISubscription&lt;T&gt;)
			 */
			public void activate() throws SubscriptionException {
				subscription.activate();
			}
			/**
			 * <code>1.</code> Cancels the previously created subscription(ISubscription&lt;T&gt;)<br/>
			 * <code>2.</code> Removes this ProxySubscription instance from the subscriptions((List&lt;ISubscription&lt;T&gt;&gt;))
			 */
			public void cancel() throws SubscriptionException {
				subscription.cancel();
				subscriptions.remove(this);
			}
			/**
			 * returns the previously(outside) given timeFrame(TimeFrame)
			 */
			public TimeFrame getTimeFrame() {
				return timeFrame;
			}
			/**
			 * returns whether the previously created subscription(ISubscription&lt;T&gt;) is active
			 */
			public boolean isActive() {
				return subscription.isActive();
			}
			/**
			 * <code>1.</code> Creates a proxy(IEventListener&lt;T&gt;) clone for the given listener(IEventListener&lt;T&gt;) whose instrumentSpecification(InstrumentSpecification)
			 * is always the previously given spec(InstrumentSpecification)<br/>
			 * <code>2.</code> maps that proxy(IEventListener&lt;T&gt;) into the associated listeners(Map&lt;IEventListener&lt;T&gt;,IEventListener&lt;T&gt;&gt;) using the given 
			 * listener(IEventListener&lt;T&gt;) as key<br/>
			 * <code>3.</code> Registers the proxy(IEventListener&lt;T&gt;) to listen to events from the subscription
			 */
			public void addEventListener(final IEventListener<T> listener) {
				IEventListener<T> proxy = new IEventListener<T>() {
					public void eventFired(T event) {
						T cloned = event.clone();
						cloned.setInstrumentSpecification(spec);
						listener.eventFired(cloned);
					}					
				};
				listeners.put(listener, proxy);
				subscription.addEventListener(proxy);
			}
			/**
			 * <code>1.</code> removes the listener-proxy mapping for the given listener(IEventListener&lt;T&gt;) from the associated listeners(Map&lt;IEventListener&lt;T&gt;,IEventListener&lt;T&gt;&gt;)<br/>
			 * <code>2.</code> the removed proxy listener stops listening to subscription events
			 */
			public void removeEventListener(IEventListener<T> listener) {
				IEventListener<T> proxy = listeners.get(listener);
				listeners.remove(listener);
				subscription.removeEventListener(proxy);
			}			
		};
		ISubscription<T> proxy = new ProxySubscription(spec); 
		
		subscriptions.add(proxy);
		return proxy;
	}
	
}
